iT邦幫忙

2023 iThome 鐵人賽

DAY 10
0
Modern Web

由前向後,從前端邁向全端系列 第 10

10.【從前端到全端,Nextjs+Nestjs】創建商店頁面-創建頁面與元件

  • 分享至 

  • xImage
  •  

文章重點

  • 拆分元件,並透過storybook來可視化元件
  • 使用Next.js創建動態路由
  • 初步實現頁面

本文

為了確保我們的應用程式結構清晰且易於維護,我們先從首頁進行元件拆分。首先,我們將主頁上的 CardHeader 元件拆分出來,使其可以在其他頁面或模組中重用。
https://ithelp.ithome.com.tw/upload/images/20230926/201089313zEij5olwX.png

A) ProductCard

libs\iron-components\src\lib\ProductCard\ProductCard.tsx:

import { Box, Card, Text } from "@radix-ui/themes";
import React from "react";

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface ProductCardProps {}

const ProductCard: React.FC<ProductCardProps> = () => {
	return (
		<Card className="flex:1|1|20% flex:1|1|40%@<xs">
			<Box>
				<picture>
					<img src="https://www.w3schools.com/tags/img_girl.jpg" aria-hidden alt="Sample Image" width="100%" />
				</picture>
				<Text as="div" size="2" weight="bold">
					Teodros Girmay
				</Text>
				<Text as="div" size="2" color="gray">
					Engineering
				</Text>
			</Box>
		</Card>
	)
}

export default ProductCard;

libs\iron-components\src\lib\ProductCard\ProductCard.stories.tsx:

import { Meta, StoryObj } from "@storybook/react";
import ProductCard from "./ProductCard";

const meta: Meta<typeof ProductCard> = {
	component: ProductCard,
};

export default meta;

type Story = StoryObj<typeof ProductCard>;

export const Default: Story = {
	args: {},
};

libs\iron-components\src\lib\ProductCard\index.ts:

import ProductCard from "./ProductCard";

export default ProductCard;

B) Header

libs\iron-components\src\lib\Header\Header.tsx:

import React from "react";

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface HeaderProps {}

const Header: React.FC<HeaderProps> = () => {
	return (
		<header className="flex flex-direction:row jc:space-between ai:center p:1em bg:#333 color:#fff">
			<div className="flex-shrink:0 f:1.5rem f:bold">Iron Shop</div>
			<nav className="flex-grow:1 flex m:0|1em">
				<ul className="list-style:none flex gap:20px m:0 p:0 {cursor:pointer}>li">
					<li>Home</li>
					<li>Products</li>
				</ul>
			</nav>
		</header>
	)
}

export default Header;

libs\iron-components\src\lib\Header\Header.stories.tsx:

import { Meta, StoryObj } from "@storybook/react";
import Header from "./Header";

const meta: Meta<typeof Header> = {
	component: Header,
};

export default meta;

type Story = StoryObj<typeof Header>;

export const Default: Story = {
	args: {},
};

libs\iron-components\src\lib\Header\index.ts:

import Header from "./Header";

export default Header;

現在我們打開Storybook觀察我們的元件。
https://ithelp.ithome.com.tw/upload/images/20230926/2010893189xInKnqU1.png

Header
https://ithelp.ithome.com.tw/upload/images/20230926/201089313Jpmj4SMKG.png
ProductCard
https://ithelp.ithome.com.tw/upload/images/20230926/20108931Q4b7n0JItz.png

接著,我們在libs\iron-components\src\index.ts添加導出我們元件的路徑:

export * from "./lib/Header";
export * from "./lib/ProductCard";
export * from "./lib/iron-components";

現在我們在我們的Next app中引入。現在我們稍微修改一下我們的layout和client元件

打開apps\iron-ecommerce-next\app\layout.tsx:

import { Flex } from "@radix-ui/themes";
import "@radix-ui/themes/styles.css";
import Header from "libs/iron-components/src/lib/Header";
import AppProvider from "./app-provider";
import "./global.css";

export const metadata = {
	title: "Welcome to iron-ecommerce-next",
	description: "Generated by create-nx-workspace"
};

const RootLayout = ({ children }: { children: React.ReactNode }) => {
	return (
		<html lang="en">
			<body>
				<AppProvider>
					<Flex direction="column">
						<Header />
						{children}
					</Flex>
				</AppProvider>
			</body>
		</html>
	);
};

export default RootLayout;

打開apps\iron-ecommerce-next\app\home.client.tsx:

"use client";
import { Flex } from "@radix-ui/themes";
import ProductCard from "libs/iron-components/src/lib/ProductCard";

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface Props {}

// eslint-disable-next-line no-empty-pattern
const HomeClient = ({}: Props) => {
	return (
		<Flex align="center" justify="center">
			<section className="flex flex:wrap flex-direction:row flex-basis:xs flex-basis:full@<xs gap:1rem jc:center">
				<div className="bg:blue w:100% h:10rem">Cover</div>
				{Array.from({ length: 32 }).map((_, i) => (
					<ProductCard key={i} />
				))}
			</section>
		</Flex>
	);
};

export default HomeClient;

現在我們已經將元件從頁面拆分出去了,接著,我們先將剩下的部分先完成,並且創建的部分有products、user和auth等頁面。並且下面是我們創建的結構。
https://ithelp.ithome.com.tw/upload/images/20230926/20108931DSIzwd5Qas.png

a) products

///// apps\iron-ecommerce-next\app\products\page.tsx /////

import { NextPage } from "next";
import ProductsClient from "./products.client";

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface ProductsPageProps {}

const ProductsPage: NextPage<ProductsPageProps> = () => {
	return <ProductsClient />;
};

export default ProductsPage;

///// apps\iron-ecommerce-next\app\products\products.client.tsx /////

"use client";
import { Flex } from "@radix-ui/themes";
import ProductCard from "libs/iron-components/src/lib/ProductCard";

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface ProductsProps {}

// eslint-disable-next-line no-empty-pattern
const ProductsClient = ({}: ProductsProps) => {
	return (
		<Flex align="center" justify="center">
			<section className="flex flex:wrap flex-direction:row flex-basis:xs flex-basis:full@<xs gap:1rem jc:center">
				<div className="w:100% h:10rem">Products</div>
				{Array.from({ length: 32 }).map((_, i) => (
						<ProductCard key={i} />
					))}
			</section>
		</Flex>
	);
};

export default ProductsClient;

https://ithelp.ithome.com.tw/upload/images/20230926/20108931WilewU4wvL.png

///// apps\iron-ecommerce-next\app\products\[id]\page.tsx /////

import { NextPage } from "next";
import ProductIdClient from "./productId.client";

interface ProductIdPageProps {
	params: {
		id: string;
	};
}

const ProductIdPage: NextPage<ProductIdPageProps> = ({ params }) => {
	const { id } = params;
	return <ProductIdClient productId={id} />;
};

export default ProductIdPage;


///// apps\iron-ecommerce-next\app\products\[id]\productId.client.tsx /////

"use client";
import { Flex } from "@radix-ui/themes";
import ProductCard from "libs/iron-components/src/lib/ProductCard";

interface ProductIdProps {
	productId?: string;
}

// eslint-disable-next-line no-empty-pattern
const ProductIdClient = ({productId}: ProductIdProps) => {
	return (
			<Flex align="center" justify="center">
				<section className="flex flex:wrap flex-direction:row flex-basis:xs flex-basis:full@<xs gap:1rem jc:center">
					<div className="w:100% h:10rem">Product: {productId}</div>
					<ProductCard />
				</section>
			</Flex>
	);
};

export default ProductIdClient;

https://ithelp.ithome.com.tw/upload/images/20230926/201089313oJsPGBQzj.png

b) user

///// apps\iron-ecommerce-next\app\user\[id]\page.tsx /////

import { NextPage } from "next";
import UserIdClient from "./userId.client";

interface UserIdPageProps {
	params: {
		id: string;
	};
}


const UserIdPage: NextPage<UserIdPageProps> = ({ params }) => {
	const { id } = params;
	return <UserIdClient userId={id} />;
};

export default UserIdPage;


///// apps\iron-ecommerce-next\app\user\[id]\userId.client.tsx /////

"use client";
import { Flex } from "@radix-ui/themes";

interface UserIdProps {
	userId?: string;
}

// eslint-disable-next-line no-empty-pattern
const UserIdClient = ({userId}: UserIdProps) => {
	return (
		<Flex align="center" justify="center">
			<section className="flex flex:wrap flex-direction:row flex-basis:xs flex-basis:full@<xs gap:1rem jc:center">
				<div className="w:100% h:10rem bg:grey">User {userId} Block</div>
			</section>
		</Flex>
	);
};

export default UserIdClient;


https://ithelp.ithome.com.tw/upload/images/20230926/201089317b54MBaY0a.png

///// apps\iron-ecommerce-next\app\user\auth\page.tsx /////

import { NextPage } from "next";
import UserAuthClient from "./userAuth.client";

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface UserIdPageProps {}

const UserAuthPage: NextPage<UserIdPageProps> = () => {
	return <UserAuthClient />;
};

export default UserAuthPage;


///// apps\iron-ecommerce-next\app\user\auth\userAuth.client.tsx /////

"use client";
import { Flex } from "@radix-ui/themes";

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface UserAuthProps {}

// eslint-disable-next-line no-empty-pattern
const UserAuthClient = ({}: UserAuthProps) => {
	return (
		<Flex align="center" justify="center">
			<section className="flex flex:wrap flex-direction:row flex-basis:xs flex-basis:full@<xs gap:1rem jc:center">
				<div className="w:100% h:10rem bg:grey">User Auth Block</div>
			</section>
		</Flex>
	);
};

export default UserAuthClient;

https://ithelp.ithome.com.tw/upload/images/20230926/20108931XGFsMxIOLa.png

現在我們將部分的頁面先創建出來,之後我們在實現內部的細節。

並且當我們進行更多的開發,我們可能會需要一直創建組件。手動創建這些組件可能會很耗時,並且容易出錯。為了提高效率,我們將使用 NX 提供的工具來自動化這個過程。


總結

今天,我們在Next.js應用程式中拆分和重用組件,並通過使用 Storybook,我們可以更加直觀地理解和展示我們的組件。接著我們將一些頁面先創建起來。最後,為了進一步提高我們的開發效率,明天我們將介紹如何使用NX來達成自動化創建組件。


上一篇
9.【從前端到全端,Nextjs+Nestjs】創建商店頁面-規劃和創建頁面
下一篇
11.【從前端到全端,Nextjs+Nestjs】使用Nx創建component generator
系列文
由前向後,從前端邁向全端30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
DL
iT邦新手 5 級 ‧ 2024-02-20 16:23:59

樓主你好~
我在此章節執行
git add .
pnpm run commit

一直報以下錯誤

✖ nx affected:lint:

 >  NX   Affected criteria defaulted to --base=main --head=HEAD


 >  NX   Running target lint for 3 projects:

    - iron-ecommerce-next
    - iron-ecommerce-next-e2e
    - iron-components
   
   With additional flags:
     apps/iron-ecommerce-next/app/home.client.tsx apps/iron-ecommerce-next/app/layout.tsx apps/iron-ecommerce-next/app/products/[id]/page.tsx apps/iron-ecommerce-next/app/products/[id]/productId.client.tsx apps/iron-ecommerce-next/app/products/page.tsx apps/iron-ecommerce-next/app/products/products.client.tsx apps/iron-ecommerce-next/app/user/[id]/page.tsx apps/iron-ecommerce-next/app/user/[id]/userId.client.tsx apps/iron-ecommerce-next/app/user/auth/page.tsx apps/iron-ecommerce-next/app/user/auth/userAuth.client.tsx libs/iron-components/src/index.ts libs/iron-components/src/lib/Header/Header.stories.tsx libs/iron-components/src/lib/Header/Header.tsx libs/iron-components/src/lib/Header/index.ts libs/iron-components/src/lib/ProductCard/ProductCard.stories.tsx libs/iron-components/src/lib/ProductCard/ProductCard.tsx libs/iron-components/src/lib/ProductCard/index.ts

 

> nx run iron-components:lint apps/iron-ecommerce-next/app/home.client.tsx apps/iron-ecommerce-next/app/layout.tsx apps/iron-ecommerce-next/app/products/[id]/page.tsx apps/iron-ecommerce-next/app/products/[id]/productId.client.tsx apps/iron-ecommerce-next/app/products/page.tsx apps/iron-ecommerce-next/app/products/products.client.tsx apps/iron-ecommerce-next/app/user/[id]/page.tsx apps/iron-ecommerce-next/app/user/[id]/userId.client.tsx apps/iron-ecommerce-next/app/user/auth/page.tsx apps/iron-ecommerce-next/app/user/auth/userAuth.client.tsx libs/iron-components/src/index.ts libs/iron-components/src/lib/Header/Header.stories.tsx libs/iron-components/src/lib/Header/Header.tsx libs/iron-components/src/lib/Header/index.ts libs/iron-components/src/lib/ProductCard/ProductCard.stories.tsx libs/iron-components/src/lib/ProductCard/ProductCard.tsx libs/iron-components/src/lib/ProductCard/index.ts


Linting "iron-components"...
Pages directory cannot be found at apps/iron-ecommerce-next/pages. If using a custom path, please configure with the `no-html-link-for-pages` rule in your eslint config file.

/Users/chihhao/iron-ecommerce-org/apps/iron-ecommerce-next/app/home.client.tsx
  3:1  error  Projects cannot be imported by a relative or absolute path, and must begin with a npm scope  @nx/enforce-module-boundaries

/Users/chihhao/iron-ecommerce-org/apps/iron-ecommerce-next/app/layout.tsx
  3:1  error  Projects cannot be imported by a relative or absolute path, and must begin with a npm scope  @nx/enforce-module-boundaries

/Users/chihhao/iron-ecommerce-org/apps/iron-ecommerce-next/app/products/products.client.tsx
  3:1  error  External resources cannot be imported using a relative or absolute path  @nx/enforce-module-boundaries

✖ 3 problems (3 errors, 0 warnings)

✖ 3 problems (3 errors, 0 warnings)


> nx run iron-ecommerce-next-e2e:lint apps/iron-ecommerce-next/app/home.client.tsx apps/iron-ecommerce-next/app/layout.tsx apps/iron-ecommerce-next/app/products/[id]/page.tsx apps/iron-ecommerce-next/app/products/[id]/productId.client.tsx apps/iron-ecommerce-next/app/products/page.tsx apps/iron-ecommerce-next/app/products/products.client.tsx apps/iron-ecommerce-next/app/user/[id]/page.tsx apps/iron-ecommerce-next/app/user/[id]/userId.client.tsx apps/iron-ecommerce-next/app/user/auth/page.tsx apps/iron-ecommerce-next/app/user/auth/userAuth.client.tsx libs/iron-components/src/index.ts libs/iron-components/src/lib/Header/Header.stories.tsx libs/iron-components/src/lib/Header/Header.tsx libs/iron-components/src/lib/Header/index.ts libs/iron-components/src/lib/ProductCard/ProductCard.stories.tsx libs/iron-components/src/lib/ProductCard/ProductCard.tsx libs/iron-components/src/lib/ProductCard/index.ts


Linting "iron-ecommerce-next-e2e"...
Pages directory cannot be found at apps/iron-ecommerce-next/pages. If using a custom path, please configure with the `no-html-link-for-pages` rule in your eslint config file.

/Users/chihhao/iron-ecommerce-org/apps/iron-ecommerce-next/app/home.client.tsx
  3:1  error  Projects cannot be imported by a relative or absolute path, and must begin with a npm scope  @nx/enforce-module-boundaries

/Users/chihhao/iron-ecommerce-org/apps/iron-ecommerce-next/app/layout.tsx
  3:1  error  Projects cannot be imported by a relative or absolute path, and must begin with a npm scope  @nx/enforce-module-boundaries

/Users/chihhao/iron-ecommerce-org/apps/iron-ecommerce-next/app/products/products.client.tsx
  3:1  error  External resources cannot be imported using a relative or absolute path  @nx/enforce-module-boundaries

✖ 3 problems (3 errors, 0 warnings)

✖ 3 problems (3 errors, 0 warnings)


> nx run iron-ecommerce-next:lint apps/iron-ecommerce-next/app/home.client.tsx apps/iron-ecommerce-next/app/layout.tsx apps/iron-ecommerce-next/app/products/[id]/page.tsx apps/iron-ecommerce-next/app/products/[id]/productId.client.tsx apps/iron-ecommerce-next/app/products/page.tsx apps/iron-ecommerce-next/app/products/products.client.tsx apps/iron-ecommerce-next/app/user/[id]/page.tsx apps/iron-ecommerce-next/app/user/[id]/userId.client.tsx apps/iron-ecommerce-next/app/user/auth/page.tsx apps/iron-ecommerce-next/app/user/auth/userAuth.client.tsx libs/iron-components/src/index.ts libs/iron-components/src/lib/Header/Header.stories.tsx libs/iron-components/src/lib/Header/Header.tsx libs/iron-components/src/lib/Header/index.ts libs/iron-components/src/lib/ProductCard/ProductCard.stories.tsx libs/iron-components/src/lib/ProductCard/ProductCard.tsx libs/iron-components/src/lib/ProductCard/index.ts


Linting "iron-ecommerce-next"...
Pages directory cannot be found at apps/iron-ecommerce-next/pages. If using a custom path, please configure with the `no-html-link-for-pages` rule in your eslint config file.

/Users/chihhao/iron-ecommerce-org/apps/iron-ecommerce-next/app/home.client.tsx
  3:1  error  Projects cannot be imported by a relative or absolute path, and must begin with a npm scope  @nx/enforce-module-boundaries

/Users/chihhao/iron-ecommerce-org/apps/iron-ecommerce-next/app/layout.tsx
  3:1  error  Projects cannot be imported by a relative or absolute path, and must begin with a npm scope  @nx/enforce-module-boundaries

/Users/chihhao/iron-ecommerce-org/apps/iron-ecommerce-next/app/products/products.client.tsx
  3:1  error  External resources cannot be imported using a relative or absolute path  @nx/enforce-module-boundaries

✖ 3 problems (3 errors, 0 warnings)

✖ 3 problems (3 errors, 0 warnings)

我要留言

立即登入留言